<template>
  <div class="el-transfer-panel">
    <p class="el-transfer-panel__header">
      <el-checkbox v-model="allChecked" @change="handleAllCheckedChange" :indeterminate="isIndeterminate">
        {{ title }}
        <span>{{ checkedSummary }}</span>
      </el-checkbox>
    </p>
    <div :class="['el-transfer-panel__body', hasFooter ? 'is-with-footer' : '']">
      <el-input
        v-if="filterable && !customFilter"
        class="el-transfer-panel__filter"
        v-model.trim="query"
        :placeholder="placeholder"
        @mouseenter="inputHover = true"
        @mouseleave="inputHover = false"
      >
        <template #prefix>
          <i :class="['el-input__icon', 'el-icon-' + inputIcon]" @click="clearQuery"></i>
        </template>
      </el-input>
      <slot v-else name="leftCustomFilter"></slot>
      <el-checkbox-group
        v-model="checked"
        v-show="!hasNoMatch && data.length > 0"
        :class="{ 'is-filterable': filterable }"
        class="el-transfer-panel__list"
        v-on:scroll="getMore($event)"
      >
        <el-checkbox
          class="el-transfer-panel__item"
          :label="item[keyProp]"
          :disabled="item[disabledProp]"
          :key="item[keyProp]"
          v-for="item in showData"
        >
          <option-content :option="item"></option-content>
        </el-checkbox>
      </el-checkbox-group>
      <p class="el-transfer-panel__empty" v-show="hasNoMatch">无匹配数据</p>
      <p class="el-transfer-panel__empty" v-show="data.length === 0 && !hasNoMatch">无数据</p>
    </div>
    <p class="el-transfer-panel__footer" v-if="hasFooter">
      <slot></slot>
    </p>
  </div>
</template>

<script lang="ts">
import { computed, defineComponent, reactive, watch, toRefs, getCurrentInstance } from 'vue'
import OptionContent from './option-content.vue'
export default defineComponent({
  name: 'V8TransferPanel',
  components: {
    OptionContent
  },
  props: {
    data: {
      type: Array,
      default: () => []
    },
    renderContent: {},
    placeholder: {
      type: String,
      default: ''
    },
    title: {
      type: String,
      default: ''
    },
    filterMethod: {},
    customFilterMethod: {} as any,
    leftcustomFilterData: {
      type: Object,
      default: () => ({})
    },
    format: {} as any,
    filterable: {
      type: Boolean
    },
    customFilter: {
      type: Boolean
    },
    defaultChecked: {
      type: Array,
      default: () => []
    },
    props: {} as any,
    loadDataNum: {
      type: Number,
      default: 20
    },
    hasFooter: {
      type: Boolean,
      default: false
    }
  },
  emits: ['onCheckedChange'],
  setup(props, { emit }) {
    const state = reactive({
      checked: [],
      allChecked: false,
      query: '',
      inputHover: false,
      checkChangeByUser: true,
      showData: [] as any
    })

    const instance: any = getCurrentInstance()

    const filteredData: any = computed(() => {
      return props.data.filter((item: any) => {
        if (typeof props.filterMethod === 'function') {
          return props.filterMethod(state.query, item)
        } else if (props.customFilter) {
          return props.customFilterMethod('', item)
        } else {
          const label = item[labelProp.value] || item[keyProp.value].toString()
          return label.toLowerCase().indexOf(state.query.toLowerCase()) > -1
        }
      })
    })

    const checkableData: any = computed(() => {
      return filteredData.value.filter((item: any) => !item[disabledProp.value])
    })

    const checkedSummary = computed(() => {
      const checkedLength = state.checked.length
      const dataLength = props.data.length
      if (props.format?.noChecked && props.format?.hasChecked) {
        return checkedLength > 0
          ? props.format?.hasChecked.replace(/\${checked}/g, checkedLength).replace(/\${total}/g, dataLength)
          : props.format?.noChecked.replace(/\${total}/g, dataLength)
      } else {
        return `${checkedLength}/${dataLength}`
      }
    })

    const isIndeterminate = computed(() => {
      const checkedLength = state.checked.length
      return checkedLength > 0 && checkedLength < checkableData.value.length
    })

    const hasNoMatch = computed(() => {
      if (props.customFilter) {
        return filteredData.value.length === 0
      } else {
        return state.query.length > 0 && filteredData.value.length === 0
      }
    })

    const inputIcon: any = computed(() => {
      return state.query.length > 0 && state.inputHover ? 'circle-close' : 'search'
    })

    const labelProp: any = computed(() => {
      return props.props.label || 'label'
    })

    const keyProp: any = computed(() => {
      return props.props.key || 'key'
    })

    const disabledProp: any = computed(() => {
      return props.props.disabled || 'disabled'
    })

    const loadMore = () => {
      const len = state.showData.length
      const allLength = filteredData.value.length
      if (len < allLength) {
        state.showData = [...state.showData, ...filteredData.value.slice(len, len + props.loadDataNum)]
      }
    }

    const updateAllChecked = () => {
      const checkableDataKeys: any = {}
      checkableData.value.map((item: any) => {
        checkableDataKeys[item[keyProp.value]] = true
      })
      state.allChecked =
        Object.keys(checkableDataKeys).length > 0 &&
        state.checked.length > 0 &&
        state.checked.every((item: any) => checkableDataKeys[item])
    }

    const handleAllCheckedChange = (value: any) => {
      state.checked = value ? checkableData.value.map((item: any) => item[keyProp.value]) : []
    }

    const clearQuery = () => {
      if (inputIcon.value === 'circle-close') {
        state.query = ''
      }
    }

    const getMore = (e: any) => {
      const scrollTop = e.target.scrollTop
      const scrollHeight = e.target.scrollHeight
      const offsetHeight = Math.ceil(e.target.getBoundingClientRect().height)
      const currentHeight = scrollTop + offsetHeight
      if (currentHeight >= scrollHeight) {
        loadMore()
      }
    }

    watch(
      () => state.checked,
      (newVal: any, oldVal: any) => {
        updateAllChecked()
        if (state.checkChangeByUser) {
          const movedKeys = newVal
            .concat(oldVal)
            .filter((v: any) => newVal.indexOf(v) === -1 || oldVal.indexOf(v) === -1)
          emit('onCheckedChange', newVal, movedKeys)
        } else {
          emit('onCheckedChange', newVal)
          state.checkChangeByUser = true
        }
      }
    )

    watch(
      () => props.data,
      () => {
        const checked: any = []
        const filteredDataKeys = filteredData.value.map((item: any) => item[keyProp.value])
        state.checked.forEach((item: any) => {
          if (filteredDataKeys.indexOf(item) > -1) {
            checked.push(item)
          }
        })
        state.checkChangeByUser = false
        state.checked = checked
      }
    )

    watch(
      () => checkableData.value,
      () => {
        updateAllChecked()
      }
    )

    watch(
      () => props.defaultChecked,
      (val, oldVal) => {
        if (oldVal && val.length === oldVal.length && val.every((item) => oldVal.indexOf(item) > -1)) {
          return
        }
        const checked: any = []
        const checkableDataKeys = checkableData.value.map((item: any) => item[keyProp.value]) || []
        val.forEach((item) => {
          if (checkableDataKeys.indexOf(item) > -1) {
            checked.push(item)
          }
        })
        state.checkChangeByUser = false
        state.checked = checked
      },
      { immediate: true }
    )

    watch(
      () => filteredData.value,
      () => {
        const length = state.showData.length
        state.showData = filteredData.value.slice(0, length)
        loadMore()
      }
    )

    return {
      ...toRefs(state),
      checkedSummary,
      isIndeterminate,
      hasNoMatch,
      inputIcon,
      keyProp,
      disabledProp,
      loadMore,
      handleAllCheckedChange,
      clearQuery,
      getMore
    }
  }
})
</script>
